/*
 * ConcretePlace.java
 * 
 * Created on Jun 8, 2007, 8:17:20 PM
 * 
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.infomancers.workflow.concrete;

import com.infomancers.workflow.Place;
import com.infomancers.workflow.PlaceListener;
import com.infomancers.workflow.Token;
import com.infomancers.workflow.Transition;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 *
 * @author aviadbd
 */
public class ConcretePlace implements Place {
    
    private Collection<PlaceListener> listeners;
    private Map<String, String> properties;
    
    private List<Transition> outputTransitions;
    private Map<Token, Transition> tokensToLocks = new HashMap<Token, Transition>();
    
    private String description;
    
    public ConcretePlace(String desc) {
        this.description = desc;
    }
    
    public void addPlaceListener(PlaceListener listener) {
        if (listeners == null) {
            listeners = new CopyOnWriteArrayList();
        }
        
        listeners.add(listener);
    }
    
    public void removePlaceListener(PlaceListener listener) {
        if (listeners != null) {
            listeners.remove(listener);
        }
    }
    
    /**
     * Add a new token to this place, in order to be executed by one
     * of the output transitions as soon as possible.
     *
     * @param token The token added to the place.
     */
    public final void addToken(Token token) {
        synchronized (tokensToLocks) {
            if (!tokensToLocks.containsKey(token)) {
                tokensToLocks.put(token, null);
            }
        }
        
        if (outputTransitions != null) {
            for (Transition t : outputTransitions) {
                if (t.tryFire(token)) {
                    break;
                }
            }
            
        }
        tokenAdded(token);
    }
    
    protected void tokenAdded(Token token) {
        if (listeners != null) {
            for (PlaceListener listener : listeners) {
                listener.tokenAdded(this, token);
            }
        }
    }
    
    /**
     * Removes a token by request from a transition. The token
     * can only be removed if the transition had previously
     * locked it for removal.
     *
     * @param transition The transition requesting the removal.
     * @param token The token to be removed.
     */
    public void removeToken(Transition transition, Token token) {
        boolean removed = false;
        
        if (outputTransitions.contains(transition)) {
            synchronized (tokensToLocks) {
                if (tokensToLocks.get(token) == transition) {
                    tokensToLocks.remove(token);
                    removed = true;
                }
            }
        }
        
        if (removed) {
            tokenRemoved(token);
        }
    }
    
    protected void tokenRemoved(Token token) {
        if (listeners != null) {
            for (PlaceListener listener : listeners) {
                listener.tokenRemoved(this, token);
            }
        }
    }
    
    public boolean lockToken(Transition transition, Token token) {
        if (outputTransitions.contains(transition) && tokensToLocks.containsKey(token)) {
            synchronized (tokensToLocks) {
                Transition lockingTransition = tokensToLocks.get(token);
                
                if (lockingTransition == null) {
                    tokensToLocks.put(token, transition);
                    return true;
                } else if (lockingTransition == transition) {
                    return true;
                } else {
                    return false;
                }
            }
        } else {
            return false;
        }
    }
    
    public void unlockToken(Transition transition, Token token) {
        synchronized (tokensToLocks) {
            if (tokensToLocks.get(token) == transition) {
                tokensToLocks.put(token, null);
            }
        }
    }
    
    public void setOutputTransitions(List<Transition> outputTransitions) {
        if (this.outputTransitions == null) {
            this.outputTransitions = new LinkedList(outputTransitions);
        } else {
            this.outputTransitions.addAll(outputTransitions);
        }
    }
    
    public List<Transition> getOutputTransitions() {
        return outputTransitions;
    }
    
    public Map<String, String> getProperties() {
        if (properties == null) {
            properties = new TreeMap<String, String>();
        }
        
        return properties;
    }
    
    @Override
    public String toString() {
        return description;
    }
}
